package com.gitee.wsl.security.hash.bcrypt

import com.gitee.wsl.io.Charset
import com.gitee.wsl.io.buffer.ByteBuffer
import com.gitee.wsl.lang.bytearray.Bytes
import com.gitee.wsl.security.hash.bcrypt.BCrypt.SEPARATOR
import com.gitee.wsl.text.format.NumberFormatStyle


/**
 * A simple parser which is able to parse Modular Crypt Format specifically for bcrypt.
 *
 *
 * It will gather the parts of the format:
 *
 *  * version
 *  * cost factor
 *  * salt (decoded)
 *  * hash (decoded)
 *
 *
 * see: [BCryptFormatter]
 * see: [modular_crypt_format](https://passlib.readthedocs.io/en/stable/modular_crypt_format.html)
 */
interface BCryptParser {
    /**
     * Read and parse given bcrypt hash
     *
     * @param bcryptHash raw UTF-8 encoded byte array of the encoded hash
     * @return decoded parts of the bcrypt hash string
     * @throws IllegalBCryptFormatException if the format is not correct Modular Crypt Format
     */
    @Throws(IllegalBCryptFormatException::class)
    fun parse(bcryptHash: ByteArray): BCrypt.HashData

    /**
     * Default implementation
     */
    class Default internal constructor(private val encoder: Radix64Encoder, private val defaultCharset: Charset = Charset.UTF8) : BCryptParser {


        @Throws(IllegalBCryptFormatException::class)
        override fun parse(bcryptHash: ByteArray): BCrypt.HashData {
            require(bcryptHash.isNotEmpty()) { "must provide non-null, non-empty hash" }

            if (bcryptHash.size < 7) {
                throw IllegalBCryptFormatException("hash prefix meta must be at least 7 bytes long e.g. '$2a$10$'")
            }

            val byteBuffer: ByteBuffer = ByteBuffer(bcryptHash)

            if (byteBuffer.get() != SEPARATOR) {
                throw IllegalBCryptFormatException("hash must start with " + SEPARATOR.toInt().toChar())
            }

            var usedVersion: BCrypt.Version? = null
            for (versionToTest in BCrypt.Version.SUPPORTED_VERSIONS) {
                for (i in versionToTest.versionIdentifier!!.indices) {
                    if (byteBuffer.get() != versionToTest.versionIdentifier[i]) {
                        byteBuffer.position = byteBuffer.position - (i + 1)
                        break
                    }

                    if (i == versionToTest.versionIdentifier.size - 1) {
                        usedVersion = versionToTest
                    }
                }
                if (usedVersion != null) break
            }

            if (usedVersion == null) {
                throw IllegalBCryptFormatException("unknown bcrypt version")
            }

            if (byteBuffer.get() != SEPARATOR) {
                throw IllegalBCryptFormatException(
                    "expected separator " + SEPARATOR.toInt().toChar() + " after version identifier and before cost factor"
                )
            }

            val costBytes = byteArrayOf(byteBuffer.get(), byteBuffer.get())

            val parsedCostFactor: Int
            try {
                parsedCostFactor = defaultCharset.decode(costBytes).toString().toInt()
            } catch (e: NumberFormatException) {
                throw IllegalBCryptFormatException(
                    "cannot parse cost factor '" + defaultCharset.decode(costBytes) + "'"
                )
            }

            if (byteBuffer.get() != SEPARATOR) {
                throw IllegalBCryptFormatException(
                    "expected separator " + SEPARATOR.toInt().toChar() + " after cost factor"
                )
            }

            if (bcryptHash.size != 7 + 22 + 31) {
                throw IllegalBCryptFormatException("hash expected to be exactly 60 bytes")
            }

            val salt = ByteArray(22)
            val hash = ByteArray(31)
            byteBuffer.get(salt)
            byteBuffer.get(hash)

            return BCrypt.HashData(parsedCostFactor, usedVersion, encoder.decode(salt), encoder.decode(hash))
        }
    }
}
